/* kernel/mm.c 
 * 
 * This file is part of xnix. 
 * 
 * XNIX is free software: you can redistribute it and/or modify 
 * it under the terms of the GNU General Public License as published by 
 * the Free Software Foundation, either version 3 of the License, or 
 * (at your option) any later version. 
 * 
 * XNIX is distributed in the hope that it will be useful, 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
 * GNU General Public License for more details. 
 * 
 * You should have received a copy of the GNU General Public License 
 * along with XNIX. If not, see <https://www.gnu.org/licenses/>
 */ 




#include <kernel/init.h>
#include <kernel/module.h>
#include <kernel/errno.h>

#include <kernel/mm.h>
#include <kernel/panic.h>

#include <kernel/klist.h>
#include <kernel/kbitm.h>

#include <kernel/archinfo.h>
#include <kernel/arch_mm.h>

#include <arch/string.h>

#include <sys/types.h>


__archptr_t total_ram = 0;
__archptr_t buddy_start;

struct klist_node buddy_pages[NRBUDDYLISTS];

struct klist_node boot_nodes[NRBUDDYLISTS][NPAGES_PER_LIST];

struct mempool_bmb *klist_node_pool;


int __init mm_init(void) {
	int i, j;
	__archptr_t addr, size;

	buddy_start = PAGE_ALIGNUP((__archptr_t) &_end);

	if (!total_ram) 
		panic("mm_init: called before arch_mm_init calls. ");

	/* initial those page lists */

	for (i = 0; i < NRBUDDYLISTS; i++) 
		klist_init(buddy_pages + i);

	/* and join pages into these */

	addr = buddy_start;

	for (i = 0; i < NRBUDDYLISTS; i++) {
		size = PAGE_SIZE << i;

		for (j = 0; j < NPAGES_PER_LIST; j++) {
			klist_init(boot_nodes[i] + j);

#define curnode 	(boot_nodes[i] + j)
			curnode->payload = (void *) addr + size * j;

			/* we use same values in nodes, then 
			 * we can use klist_find_value(1) to 
			 * get the first page. :-)
			 * */
			curnode->value = BUDDY_FREE;

			klist_add(buddy_pages + i, curnode);
		}

		/* << 2: * NPAGES_PER_LIST */
		addr += size << 2;
	}

	klist_node_pool = mmp_bmb_create(sizeof(struct klist_node), 0);

	return 0;
}



void *getfreepages(int order, int gfp_flag) {
	if (order > NPAGES_PER_LIST) 
		return NULL;

	/* first search in the list, if empty, split and put 
	 * it into the list */
	
	struct klist_node *fp = klist_find_value(buddy_pages + order, BUDDY_FREE);

	if (fp) {
		fp->value = BUDDY_USING;
		return fp->payload;
	}

	return NULL;
}


int freepage(int order, void *p) {
	if (order > NPAGES_PER_LIST) 
		return -EINVAL;

	if (!p) 
		return -EFAULT;

	struct klist_node *node = buddy_pages + order;

	for (;;) {
		if (node->payload == p) 
			break;

		node = node->next;

		if (node == buddy_pages + order) 
			return -EFAULT;
	}

	node->value = BUDDY_FREE;

	return 0;
}


struct mempool_bmb *mmp_bmb_create(__uint32_t object_size, __uint32_t osorder) {
	struct mempool_bmb *pool = getfreepages(0, GFP_KERNEL);

	if (!pool) 
		return NULL;

	memset(pool, 0, PAGE_SIZE);

	__uint32_t nobjs = PAGE_SIZE - sizeof(struct mempool_bmb);

	nobjs = nobjs / 9 * 8;

	if (osorder) 
		nobjs >>= osorder;

	else 
		nobjs /= object_size;

	pool->objsize = object_size;
	pool->objsz_order = osorder;

	pool->nobjs = nobjs;

	pool->entry_addr = pool;
	pool->entry_addr += sizeof(struct mempool_bmb);

	pool->bm = pool;
	pool->bm += PAGE_SIZE - ((nobjs * object_size) >> 3) - 1;

	return pool;
}


void *mmp_bmb_alloc(struct mempool_bmb *pool) {
	int i;
	void *p;

	if (!pool) 
		return NULL;

	for (i = 0; bm_read(pool->bm, i) && i < pool->nobjs; i++);

	if (i == pool->nobjs) 
		return NULL;

	bm_set(pool->bm, i);

	p = pool->entry_addr;

	if (pool->objsz_order) 
		return p + (i << pool->objsz_order);


	return p + i * pool->objsize;
}


int mmp_bmb_free(void *p) {
	if (!p) 
		return -EFAULT;

	struct mempool_bmb *pool = (void *) ((__archptr_t) p & ~PAGE_MASK);

	int index = p - pool->entry_addr;

	if (pool->objsz_order) 
		index >>= pool->objsz_order;

	else 
		index /= pool->objsize;

	bm_clear(pool->bm, index);

	return 0;
}


kernel_init(mm_init);

